﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Audio;

namespace ScreenResolutionMaintenance
{
    /// <summary>
    /// Quick sound manager, automaticaly registers with the Games components and also with as a Game service.
    /// </summary>
    public class SoundManager : GameComponent
    {
        /// <summary>
        /// Maximum volume a music can have
        /// </summary>
        public float MaxMusicVolume { get; set; }

        /// <summary>
        /// Maxium volume for any sound effect.
        /// </summary>
        public float MaxSFXVolume { get; set; }

        /// <summary>
        /// Music is presumed to be looped
        /// </summary>
        protected Dictionary<string, SoundEffectInstance> Music = new Dictionary<string, SoundEffectInstance>();

        /// <summary>
        /// Sound effects
        /// </summary>
        protected Dictionary<string, SoundEffectInstance> SFX = new Dictionary<string, SoundEffectInstance>();

        /// <summary>
        /// Sound effects that can be played over it's self..
        /// </summary>
        protected Dictionary<string, SoundEffect> SFXInstant = new Dictionary<string, SoundEffect>();

        /// <summary>
        /// Master list.
        /// </summary>
        protected List<SoundEffectInstance> master = new List<SoundEffectInstance>();

        public bool FadeMusicOut { get; set; }
        public bool FadeMusicIn { get; set; }
        public float FadeMusicSpeed { get; set; } 
        
        /// <summary>
        /// Easy short hand propertry to get at Game.Content.
        /// </summary>
        protected ContentManager Content { get { return Game.Content; } }

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="game">Game</param>
        public SoundManager(Game game)
            : base(game)
        {
            MaxMusicVolume = 1f;
            MaxSFXVolume = 1f;
            FadeMusicSpeed = .01f;

            // Auto register 
            Game.Components.Add(this);
            Game.Services.AddService(this.GetType(), this);
        }

        /// <summary>
        /// ctoe
        /// </summary>
        /// <param name="game">Game</param>
        /// <param name="maxMusicVolume"></param>
        /// <param name="maxSFXVolume"></param>
        public SoundManager(Game game, float maxMusicVolume = 1f, float maxSFXVolume = 1f, float fadeMusicSpeed = .01f)
            : this(game)
        {
            MaxMusicVolume = maxMusicVolume;
            MaxSFXVolume = maxSFXVolume;
            FadeMusicSpeed = fadeMusicSpeed;
        }

        #region Methods to control SFX

        /// <summary>
        /// Fades all music out
        /// </summary>
        public void FadeALLMusicOut()
        {
            FadeMusicOut = true;
        }

        /// <summary>
        /// Fades all music in
        /// </summary>
        public void FadeALLMusicIn()
        {
            FadeMusicIn = true;
        }

        /// <summary>
        /// Method to load a SFX
        /// </summary>
        /// <param name="name">Name the sound effect will be known by</param>
        /// <param name="assetLocation">Location of the asset to be loaded</param>
        public void LoadSFX(string name, string assetLocation)
        {
            SoundEffectInstance instance = Content.Load<SoundEffect>(assetLocation).CreateInstance();

            if (SFX.ContainsKey(name)) // Overwrite it
                SFX[name] = instance;
            else
                SFX.Add(name, instance);

            // Add it to instant list too
            if (SFXInstant.ContainsKey(name))
                SFXInstant[name] = Content.Load<SoundEffect>(assetLocation);
            else
                SFXInstant.Add(name, Content.Load<SoundEffect>(assetLocation));

            if (!master.Contains(instance))
                master.Add(instance);
           
        }

        /// <summary>
        /// Method to play SFX
        /// </summary>
        /// <param name="name">SFX Name</param>
        /// <param name="volume">Defaults to 1 but will not exceede MaxSFXVolume</param>
        /// <param name="pan">Defaults to 0</param>
        /// <param name="pitch">Defaults to 0</param>
        /// <param name="listener">Array of listeners, default is null if no 3D is to be applied.</param>
        /// <param name="emitter">Emitter, default is null if no 3D is to be applied</param>
        public void PlaySFX(string name, float volume = 1, float pan = 0, float pitch = 0, AudioListener[] listener = null, AudioEmitter emitter = null)
        {
            if (SFX.ContainsKey(name))
            {
                SFX[name].Volume = volume > MaxSFXVolume ? MaxSFXVolume : volume;
                SFX[name].Pan = pan;
                SFX[name].Pitch = pitch;

                if (listener != null && emitter != null)
                    SFX[name].Apply3D(listener, emitter);

                SFX[name].Play();
            }            
        }

        /// <summary>
        /// Plays the sound feect so it can be played over it's self..
        /// </summary>
        /// <param name="name">SFX Name</param>
        /// <param name="volume">Defaults to 1 but will not exceede MaxSFXVolume</param>
        /// <param name="pan">Defaults to 0</param>
        /// <param name="pitch">Defaults to 0</param>
        public void PlayInstantSFX(string name, float volume = 1, float pan = 0, float pitch = 0)
        {
            if (SFXInstant.ContainsKey(name))
                SFXInstant[name].Play(volume > MaxSFXVolume ? MaxSFXVolume : volume, pitch, pan);
        }

        /// <summary>
        /// Method to apply 3D audio to ALL sound effects
        /// </summary>
        /// <param name="listener">Array of listeners</param>
        /// <param name="emitter">Emitter</param>
        public void Apply3DToALLSFX(AudioListener[] listener, AudioEmitter emitter)
        {
            foreach (SoundEffectInstance sound in SFX.Values)
                sound.Apply3D(listener, emitter);
        }        

        #endregion

        #region Methods to control Music

        /// <summary>
        /// Method to load music
        /// </summary>
        /// <param name="title">Name the music will be known by</param>
        /// <param name="assetLocation">Location of the asset to be loaded</param>
        /// <param name="loop">Default's to true</param>
        /// <param name="volume">Defaults to 1 but will not exceede MaxMusicVolume</param>
        public void LoadMusic(string title, string assetLocation, float volume = 1, bool loop = true)
        {
            SoundEffectInstance instance = Content.Load<SoundEffect>(assetLocation).CreateInstance();
            instance.Volume = volume > MaxMusicVolume ? MaxMusicVolume : volume;
            instance.IsLooped = loop;

            if (Music.ContainsKey(title)) // Overwrite it
                Music[title] = instance;
            else
                Music.Add(title, instance);

            if (!master.Contains(instance))
                master.Add(instance);
        }

        /// <summary>
        /// Alters the volumn on all playing music
        /// </summary>
        /// <param name="changeVolumneBy">value to alter it by &gt; 0 to go up &lt; 0 to go down</param>
        public void AlterALLPlayingMusicVolume(float changeVolumneBy)
        {
            List<SoundEffectInstance> playing = Music.Values.Where(sound => sound.State == SoundState.Playing).ToList();

            int mcnt = playing.Count;

            for (int m = 0; m < mcnt; m++)
                playing[m].Volume = MathHelper.Clamp(playing[m].Volume + changeVolumneBy, 0, MaxMusicVolume);

        }
        /// <summary>
        /// Gets a list of all the music currently playing
        /// </summary>
        /// <returns></returns>
        public List<string> MusicCurrentlyPlaying()
        {
            return Music.Keys.Where(key => Music[key].State == SoundState.Playing).ToList();
        }

        /// <summary>
        /// Returns true if music is playing
        /// </summary>
        /// <param name="title">music title</param>
        /// <returns></returns>
        public bool IsMusicPlaying(string title)
        {
            if (Music.ContainsKey(title))
                return Music[title].State == SoundState.Playing;

            return false;
        }

        /// <summary>
        /// Returns true if music is stopped
        /// </summary>
        /// <param name="title">music title</param>
        /// <returns></returns>
        public bool IsMusicStopped(string title)
        {
            if (Music.ContainsKey(title))
                return Music[title].State == SoundState.Stopped;

            return false;
        }

        /// <summary>
        /// Returns true if music is paused
        /// </summary>
        /// <param name="title">music title</param>
        /// <returns></returns>
        public bool IsMusicPaused(string title)
        {
            if (Music.ContainsKey(title))
                return Music[title].State == SoundState.Paused;

            return false;
        }

        /// <summary>
        /// Method to play music
        /// </summary>
        /// <param name="title">Title given to music when loaded.</param>
        public void PlayMusic(string title)
        {
            if (Music.ContainsKey(title))
            {
                switch (Music[title].State)
                {
                    case SoundState.Paused:
                        Music[title].Resume();
                        break;
                    case SoundState.Stopped:
                        Music[title].Play();
                        break;
                }
            }
        }

        /// <summary>
        /// Method to play music
        /// </summary>
        /// <param name="title">Title given to music when loaded.</param>
        /// <param name="volume">Volume to be played at</param>
        public void PlayMusic(string title, float volume = 1)
        {
            if (Music.ContainsKey(title))
            {
                Music[title].Volume = volume > MaxMusicVolume ? MaxMusicVolume : volume;

                switch (Music[title].State)
                {
                    case SoundState.Paused:
                        Music[title].Resume();
                        break;
                    case SoundState.Stopped:
                        Music[title].Play();
                        break;
                }
            }
        }

        /// <summary>
        /// Method to stop music
        /// </summary>
        /// <param name="title">Title given to music when loaded.</param>
        public void StopMusic(string title)
        {
            if (Music.ContainsKey(title))
            {
                switch (Music[title].State)
                {
                    case SoundState.Playing:
                        Music[title].Stop();
                        break;
                }
            }
        }

        /// <summary>
        /// Pauses a title if it's playing
        /// </summary>
        /// <param name="title">Title given to music when loaded.</param>
        public void PauseMusic(string title)
        {
            if (Music.ContainsKey(title))
            {
                switch (Music[title].State)
                {
                    case SoundState.Playing:
                        Music[title].Pause();
                        break;
                }
            }
        }

        #endregion

        #region Methods to control ALL sound
        /// <summary>
        /// Stops ALL music and sfx
        /// </summary>
        public void StopALL()
        {
            List<SoundEffectInstance> playing = master.Where(sound => sound.State == SoundState.Playing).ToList();

            foreach (SoundEffectInstance sound in playing)
                sound.Stop();

        }

        /// <summary>
        /// Pauses ALL music and SFX
        /// </summary>
        public void PauseAll()
        {
            List<SoundEffectInstance> playing = master.Where(sound => sound.State == SoundState.Playing).ToList();

            foreach (SoundEffectInstance sound in playing)
                sound.Pause();
        }

        /// <summary>
        /// Resumes ALL music and SFX
        /// </summary>
        public void ResumeAll()
        {
            List<SoundEffectInstance> paused = master.Where(sound => sound.State == SoundState.Paused).ToList();

            foreach (SoundEffectInstance sound in paused)
                sound.Resume();
        }
        
        #endregion

        #region Overrides
        /// <summary>
        /// Override of the components Update method
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            if (FadeMusicOut)
            {
                AlterALLPlayingMusicVolume(-FadeMusicSpeed);
                // are they all at 0?
                if (Music.Values.Where(music => music.State == SoundState.Playing && music.Volume == 0).ToList().Count > 0)
                    FadeMusicOut = false;
            }
            else
            {
                if (FadeMusicIn)
                {
                    // are they all at MaxMusicVolume?
                    if (Music.Values.Where(music => music.State == SoundState.Playing && music.Volume == MaxMusicVolume).ToList().Count > 0)
                        FadeMusicIn = false;

                    AlterALLPlayingMusicVolume(FadeMusicSpeed);
                }
            }
        }
        #endregion
    }
}
